Skip to content

Fix auto scroll to bottom when exiting a thread#6165

Merged
aleksandar-apostolov merged 1 commit into
developfrom
bug/fix_auto_scroll_to_bottom_when_exiting_a_thread
Feb 18, 2026
Merged

Fix auto scroll to bottom when exiting a thread#6165
aleksandar-apostolov merged 1 commit into
developfrom
bug/fix_auto_scroll_to_bottom_when_exiting_a_thread

Conversation

@VelikovPetar
Copy link
Copy Markdown
Contributor

@VelikovPetar VelikovPetar commented Feb 18, 2026

Goal

Fix a bug where the message list would automatically scroll to the bottom when exiting a thread, losing the user's scroll position in the main message list.

Implementation

The issue was in DefaultMessagesHelperContent where lastScrollToBottomOnNewMessage state was shared between thread and main list contexts. When exiting a thread:

  1. lastScrollToBottomOnNewMessage still held the thread's last state
  2. The main list's newMessageState was different
  3. This triggered the scroll-to-bottom logic inappropriately

The fix adds isMessageInThread as an input to rememberSaveable. When the context switches (thread ↔ main list):

  • The state is re-initialized with the current newMessageState
  • Since lastScrollToBottomOnNewMessage equals newMessageState, the comparison fails
  • No unwanted scroll-to-bottom occurs

🎨 UI Changes

Before After
thread-scroll-before.mp4
thread-scroll-after.mp4

Testing

  1. Open a channel with messages
  2. Scroll up in the message list (away from the bottom)
  3. Enter a thread
  4. Exit the thread
  5. Verify the scroll position is preserved (not scrolled to bottom)

Summary by CodeRabbit

  • Bug Fixes
    • Improved scroll-to-bottom button behavior to correctly manage state when viewing threaded messages, ensuring proper scrolling context based on message thread status.

Co-Authored-By: Claude <noreply@anthropic.com>
@VelikovPetar VelikovPetar added the pr:bug Bug fix label Feb 18, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Feb 18, 2026

PR checklist ✅

All required conditions are satisfied:

  • Title length is OK (or ignored by label).
  • At least one pr: label exists.
  • Sections ### Goal, ### Implementation, and ### Testing are filled.

🎉 Great job! This PR is ready for review.

@VelikovPetar VelikovPetar marked this pull request as ready for review February 18, 2026 09:13
@VelikovPetar VelikovPetar requested a review from a team as a code owner February 18, 2026 09:13
@github-actions
Copy link
Copy Markdown
Contributor

SDK Size Comparison 📏

SDK Before After Difference Status
stream-chat-android-client 5.26 MB 5.26 MB 0.00 MB 🟢
stream-chat-android-offline 5.48 MB 5.48 MB 0.00 MB 🟢
stream-chat-android-ui-components 10.63 MB 10.63 MB 0.00 MB 🟢
stream-chat-android-compose 12.85 MB 12.85 MB 0.00 MB 🟢

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Feb 18, 2026

Walkthrough

The change modifies how the scroll-to-bottom state is persisted by tying it to thread context via an additional condition in rememberSaveable, and reformats the scroll button click handler from multi-line to single-line while maintaining identical functionality.

Changes

Cohort / File(s) Summary
Scroll State Management
stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/messages/list/Messages.kt
Added isMessageInThread condition to lastScrollToBottomOnNewMessage state preservation to ensure scroll behavior is scoped per thread context. Reformatted ScrollToBottomButton click handler from multi-line coroutine block to single-line equivalent.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Poem

🐰 A rabbit hops through threads with grace,
State remembers its proper place,
Each message scrolls down to the start,
Code cleaner, flows from the heart!

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and accurately summarizes the main change: fixing the auto-scroll issue when exiting a thread.
Description check ✅ Passed The description includes all key sections: Goal, Implementation with detailed explanation, UI Changes with before/after videos, and Testing steps. The description is complete and well-structured.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch bug/fix_auto_scroll_to_bottom_when_exiting_a_thread

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/messages/list/Messages.kt (2)

313-318: Fix is correct — consider updating the accompanying comment to document the new key

The isMessageInThread key causes rememberSaveable to discard the saved state and re-run mutableStateOf(newMessageState) whenever the thread context changes. Because lastScrollToBottomOnNewMessage is then equal to the current newMessageState, the LaunchedEffect at line 321 never sees a difference on the first evaluation, which is exactly what prevents the spurious scroll-to-bottom. The approach is sound for both the bug scenario and config-change resilience.

The comment block above (lines 310–312) only mentions the config-change rationale. A one-liner noting why isMessageInThread is also a key input would help future maintainers:

✏️ Suggested comment update
     // Keep track of the last new message state that triggered a scroll to bottom.
     // If a configuration change happens, we want to keep the same state
     // and not scroll to bottom again if the newMessageState is the same as before the configuration change.
+    // The isMessageInThread key ensures the state is reset when switching between thread and main list contexts,
+    // preventing a stale comparison from triggering an unintended scroll after exiting a thread.
     var lastScrollToBottomOnNewMessage by rememberSaveable(
         isMessageInThread,
         saver = MutableStateNewMessageStateSaver,
     ) {
         mutableStateOf(newMessageState)
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/messages/list/Messages.kt`
around lines 313 - 318, Update the comment above the rememberSaveable block to
explicitly note that including isMessageInThread as a key causes the saved state
for lastScrollToBottomOnNewMessage (created via rememberSaveable and
mutableStateOf(newMessageState)) to be discarded when thread context changes,
ensuring the state is reset to newMessageState so the LaunchedEffect that
compares it sees no spurious difference on first evaluation; reference the
rememberSaveable call, the isMessageInThread key,
lastScrollToBottomOnNewMessage, newMessageState, and the LaunchedEffect in the
comment so future maintainers understand both the config-change and
thread-context rationale.

280-355: Consider adding a functional test (and Paparazzi snapshot) for the thread-exit scroll-position preservation

The PR checklist notes tests are unchecked. Given this is a scroll-state bug, a lightweight instrumented or unit-composed test that verifies lastScrollToBottomOnNewMessage is not stale after toggling isMessageInThread would guard against regressions. Based on learnings, Compose regressions in this module should also be covered by Paparazzi snapshots via verifyPaparazziDebug.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/messages/list/Messages.kt`
around lines 280 - 355, Add a lightweight Compose unit/instrumented test plus a
Paparazzi snapshot that verifies the scroll-position preservation logic in
DefaultMessagesHelperContent when toggling thread state: create a test that
renders DefaultMessagesHelperContent (or the Messages container that uses it),
initialize messagesState so lastScrollToBottomOnNewMessage is set, toggle
isMessageInThread by changing messagesState.parentMessageId (enter/exit thread)
and then trigger a newMessageState change; assert that the component does not
re-scroll when newMessageState equals the previous saved state (i.e.,
lastScrollToBottomOnNewMessage is preserved) and add a verifyPaparazziDebug
snapshot to capture the UI before/after the toggle to prevent regressions;
target helpers like shouldScrollToBottomOnNewMessage,
lastScrollToBottomOnNewMessage, focusedItemIndex behavior, and the
ScrollToBottomButton visibility in your test.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In
`@stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/messages/list/Messages.kt`:
- Around line 313-318: Update the comment above the rememberSaveable block to
explicitly note that including isMessageInThread as a key causes the saved state
for lastScrollToBottomOnNewMessage (created via rememberSaveable and
mutableStateOf(newMessageState)) to be discarded when thread context changes,
ensuring the state is reset to newMessageState so the LaunchedEffect that
compares it sees no spurious difference on first evaluation; reference the
rememberSaveable call, the isMessageInThread key,
lastScrollToBottomOnNewMessage, newMessageState, and the LaunchedEffect in the
comment so future maintainers understand both the config-change and
thread-context rationale.
- Around line 280-355: Add a lightweight Compose unit/instrumented test plus a
Paparazzi snapshot that verifies the scroll-position preservation logic in
DefaultMessagesHelperContent when toggling thread state: create a test that
renders DefaultMessagesHelperContent (or the Messages container that uses it),
initialize messagesState so lastScrollToBottomOnNewMessage is set, toggle
isMessageInThread by changing messagesState.parentMessageId (enter/exit thread)
and then trigger a newMessageState change; assert that the component does not
re-scroll when newMessageState equals the previous saved state (i.e.,
lastScrollToBottomOnNewMessage is preserved) and add a verifyPaparazziDebug
snapshot to capture the UI before/after the toggle to prevent regressions;
target helpers like shouldScrollToBottomOnNewMessage,
lastScrollToBottomOnNewMessage, focusedItemIndex behavior, and the
ScrollToBottomButton visibility in your test.

@sonarqubecloud
Copy link
Copy Markdown

Copy link
Copy Markdown
Contributor

@aleksandar-apostolov aleksandar-apostolov left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clean fix. LGTM

@aleksandar-apostolov aleksandar-apostolov merged commit 714ea4f into develop Feb 18, 2026
21 checks passed
@aleksandar-apostolov aleksandar-apostolov deleted the bug/fix_auto_scroll_to_bottom_when_exiting_a_thread branch February 18, 2026 19:06
@stream-public-bot stream-public-bot added the released Included in a release label Feb 24, 2026
@stream-public-bot
Copy link
Copy Markdown
Contributor

🚀 Available in v6.32.4

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

pr:bug Bug fix released Included in a release

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants